package co.fitstart.admin.service.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.demo2do.core.BusinessException;
import com.demo2do.core.persistence.GenericDaoSupport;
import com.demo2do.core.persistence.annotation.Dynamic;
import com.demo2do.core.support.Page;
import com.demo2do.core.support.Result;

import co.fitstart.admin.dao.CourseRoundDao;
import co.fitstart.admin.dao.OrderDao;
import co.fitstart.admin.entity.search.OnlineRoundSearch;
import co.fitstart.admin.handler.FileHandler;
import co.fitstart.admin.service.OnlineRoundService;
import co.fitstart.entity.Constant;
import co.fitstart.entity.course.CourseType;
import co.fitstart.entity.course.round.CourseRound;
import co.fitstart.entity.course.round.CourseRoundGroup;
import co.fitstart.entity.course.round.CourseRoundResource;
import co.fitstart.entity.course.round.OnlineRoundGroupStatus;
import co.fitstart.entity.course.round.RoundStatus;
import co.fitstart.entity.order.Order;
import co.fitstart.entity.order.OrderInvestigateRecord;
import co.fitstart.entity.order.OrderStatus;
import co.fitstart.entity.order.OrderType;
import co.fitstart.entity.product.Product;
import co.fitstart.entity.user.User;

/**
 * 
 * @author lidan
 *
 */
@Service("onlineRoundService")
public class OnlineRoundServiceImpl implements OnlineRoundService{
    
    @Dynamic
    private CourseRoundDao courseRoundDao;
    
    @Autowired
    private GenericDaoSupport genericDaoSupport;
    
    @Dynamic
    private OrderDao orderDao;
    
    @Value("#{config['file.upload.path']}")
    private String uploadPath;
    
    @Autowired
    private FileHandler fileHandler;
    
    private static final Logger logger = LoggerFactory.getLogger(OnlineRoundServiceImpl.class);
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.course.OnlineRoundService#list(co.fitstart.entity.course.CourseType, com.demo2do.core.support.Page)
     */
    public List<CourseRound> list(CourseType courseType, Page page) {
        List<CourseRound> result = courseRoundDao.listOnlineRound(courseType, page.getBeginIndex(), page.getMaxResultRecords());
        
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.OnlineRoundService#list(java.lang.String, com.demo2do.core.support.Page)
     */
    public List<CourseRound> list(String roundCode, Page page) {
        List<CourseRound> result = courseRoundDao.listRoundByCode(roundCode, RoundStatus.PREPARE, page.getBeginIndex(), page.getMaxResultRecords());
        
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.OnlineRoundService#listUnfinishCourseRound(java.lang.String, com.demo2do.core.support.Page)
     */
    public List<CourseRound> listUnfinishCourseRound(String roundCode, Page page) {
        
        List<CourseRound> result = courseRoundDao.listUnfinishRoundByCode(new OnlineRoundGroupStatus[]{OnlineRoundGroupStatus.DISALLOW_GROUP, OnlineRoundGroupStatus.UNGROUPED}, roundCode, RoundStatus.PREPARE, page.getBeginIndex(), page.getMaxResultRecords());
        
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.OnlineRoundService#listFinishRoundByGroup(java.lang.String, java.lang.String, com.demo2do.core.support.Page)
     */
    public List<CourseRound> listFinishRoundByGroup(String code, String groupName, Page page) {
        List<CourseRound> result = new ArrayList<CourseRound>();
        result = courseRoundDao.listfinishRoundByGroup(code, groupName, page.getBeginIndex(), page.getMaxResultRecords());
        
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.CourseRoundService#listCourseRoundByGroup(java.lang.String)
     */
    public Map<String, List<CourseRound>> listCourseRoundByGroup(String code) {
        List<CourseRoundGroup> groups = courseRoundDao.listCourseRoundGroup(code);
        
        if (groups.isEmpty()) {
            return this.listUnGroupedRound(code);
        } else {
            return this.listGroupedRound(code, groups);
        }
    }
    
    /**
     * 
     * @param code
     * @return
     */
    private Map<String, List<CourseRound>> listUnGroupedRound(String code) {
        Map<String, List<CourseRound>> courseRounds = new HashMap<String, List<CourseRound>>();
        courseRounds.put("全部", courseRoundDao.listRoundByCode(code, RoundStatus.PREPARE, OnlineRoundGroupStatus.GROUPING));
        return courseRounds;
    }
    
    /**
     * 
     * @param code
     * @param groups
     * @return
     */
    private Map<String, List<CourseRound>> listGroupedRound(String code, List<CourseRoundGroup> groups) {
        Map<String, List<CourseRound>> courseRounds = new HashMap<String, List<CourseRound>>();
        
        List<CourseRound> ungroupRounds = courseRoundDao.listCourseRound(OnlineRoundGroupStatus.UNGROUPED, code, RoundStatus.PREPARE);
        if (!ungroupRounds.isEmpty()) {
            courseRounds.put("未分组", ungroupRounds);
        }
        
        // put finish courseRound by courseRoundGroup
        for(CourseRoundGroup group : groups) { 
            List<CourseRound> rounds = courseRoundDao.listCourseRound(code, group.getName());
            if (!rounds.isEmpty()) {
                courseRounds.put(group.getName(), rounds);
            }
        }
        return courseRounds;
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.OnlineRoundService#search(co.fitstart.admin.entity.search.OnlineRoundSearch, java.lang.String, com.demo2do.core.support.Page)
     */
    @SuppressWarnings("unchecked")
    public List<CourseRound> search(OnlineRoundSearch onlineRoundSearch, String groupName, Page page) {
        List<CourseRound> result = new ArrayList<CourseRound>();
        if (onlineRoundSearch.isGrouped() && StringUtils.isNotEmpty(groupName)) {
            List<Long> courseRoundIds = courseRoundDao.listCourseRoundId(onlineRoundSearch.getCode(), groupName);
            if(courseRoundIds.isEmpty()) {
                return result;
            } else {
                onlineRoundSearch.editRoundIds(courseRoundIds);
            } 
        }
        result = genericDaoSupport.searchForList(onlineRoundSearch.buildSentenceTemplate(), onlineRoundSearch.buildParameters(), page.getBeginIndex(), page.getMaxResultRecords());
        page.calculate(result.size());
        return result.size() > page.getEveryPage() ? result.subList(0, page.getEveryPage()) : result;
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.course.OnlineRoundService#loadCourseRound(java.lang.Long)
     */
    public CourseRound loadCourseRound(Long courseId) {
        return genericDaoSupport.load(CourseRound.class, courseId);
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.course.OnlineRoundService#editRemarks(co.fitstart.entity.course.round.CourseRound, java.lang.String)
     */
    public void editRemarks(CourseRound courseRound, String remarks) {
        if(MapUtils.isNotEmpty(courseRound.getAttrs()) && courseRound.getAttrs().get(Constant.COURSE_REMARKS) != null) {
            genericDaoSupport.update(courseRound.editAttribute(Constant.COURSE_REMARKS, remarks));
        }else {
            genericDaoSupport.update(courseRound.addAttribute(Constant.COURSE_REMARKS, remarks));
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.course.OnlineRoundService#getOrderInvestigateRecord(co.fitstart.entity.product.Product)
     */
    public List<OrderInvestigateRecord> getOrderInvestigateRecord(Product product) {
        List<OrderStatus> orderStatus = new ArrayList<OrderStatus>();
        orderStatus.add(OrderStatus.PENDING);
        orderStatus.add(OrderStatus.PROCESSING);
        
        return orderDao.getOrderInvestigateRecord(product, OrderType.ONLINE, orderStatus);
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.OnlineRoundService#getOrderInvestigateRecord(java.lang.String)
     */
    public Map<String, List<OrderInvestigateRecord>> getOrderInvestigateRecord(String code) {
        Map<String, List<OrderInvestigateRecord>> records = new LinkedHashMap<String, List<OrderInvestigateRecord>>();
        
        // add unfinished order_investigate_record first
        List<Order> unfinishOrders = orderDao.listUnfinishOrder(new OnlineRoundGroupStatus[]{OnlineRoundGroupStatus.DISALLOW_GROUP, OnlineRoundGroupStatus.UNGROUPED}, code, RoundStatus.PREPARE);
        if (!unfinishOrders.isEmpty()) {
            List<OrderInvestigateRecord> unfinishes = orderDao.listOrderInvestigateRecord(unfinishOrders);
            records.put("未分组", unfinishes);
        }
        
        // add finished order_investigate_record by group
        List<CourseRoundGroup> groups = courseRoundDao.listCourseRoundGroup(code);
        for(CourseRoundGroup group : groups) {
            List<Long> courseRoundIds = courseRoundDao.listCourseRoundId(code, group.getName());
            if(!courseRoundIds.isEmpty()) {
                List<Order> orders = orderDao.listOrder(courseRoundIds);
                List<OrderInvestigateRecord> orderInvestigateRecords = orderDao.listOrderInvestigateRecord(orders);
                records.put(group.getName(), orderInvestigateRecords);
            }
        }
        
        return records;
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.course.OnlineRoundService#loadCourseRoundResource(java.lang.String)
     */
    public CourseRoundResource loadCourseRoundResource(String roundCode) {
        return courseRoundDao.loadCourseRoundResource(roundCode);
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.OnlineRoundService#transferCamp(co.fitstart.entity.course.round.CourseRound, co.fitstart.entity.product.Product, java.lang.String)
     */
    public void transferCamp(CourseRound courseRound, Product product, String code) {
        
        // Step1: load order
        Order order = courseRound.getOrder();
        
        // Step2: if investigate has finished and  this course has grouped then delete 
        if(courseRound.getOnlineRoundAttr().getOnlineRoundGroupStatus().isGrouped()) {
            CourseRoundGroup courseRoundGroup = courseRoundDao.loadCourseRoundGroup(courseRound);
            genericDaoSupport.update(courseRoundGroup.disable());
        }
        
        // Step3: edit order's product first
        genericDaoSupport.update(order.editProduct(product).editCourse(product.getCourse()));
        
        // Step4: if investigate has finished transfer courseRoundGroup
        if(courseRound.getOnlineRoundAttr().getOnlineRoundGroupStatus() != null && !courseRound.getOnlineRoundAttr().getOnlineRoundGroupStatus().isDisallowGroup()) {
            courseRound = this.appendCourseRoundGroup(courseRound, code);
            courseRound.editZipName();
        }
        
        // Step5: edit courseRound's product 
        genericDaoSupport.update(courseRound.transferCamp(product, code));
    }
    
    /**
     * 
     * @param courseRound
     * @param code
     * @return
     */
    @SuppressWarnings("unchecked")
    private CourseRound appendCourseRoundGroup(CourseRound courseRound, String code) {
        List<CourseRoundGroup> courseRoundGroups = courseRoundDao.listOrderedCourseRoundGroup(code);
        
        CourseRoundGroup courseRoundGroup = null;
        final String preName = courseRound.getStudent().isOnline() ? "O" : "N";
        if (!courseRoundGroups.isEmpty()) {
            courseRound.editGroupStatus(OnlineRoundGroupStatus.GROUPED);
            List<CourseRoundGroup> groups = (List<CourseRoundGroup>) CollectionUtils.select(courseRoundGroups, new Predicate() {
                public boolean evaluate(Object arg0) {
                  CourseRoundGroup group = (CourseRoundGroup) arg0;
                  String[] names = StringUtils.split(group.getName(), "SG");
                  return StringUtils.equalsIgnoreCase(names[0], preName);
                }
            });
            
            if(groups.isEmpty()) {
                courseRoundGroup = new CourseRoundGroup(preName + "SG0", code, courseRound);
            } else {
                if(courseRoundDao.getRoundAmount(code, groups.get(0).getName()) < 20) {
                    courseRoundGroup = new CourseRoundGroup(groups.get(0).getName(), code, courseRound);
                } else {
                    String[] names = StringUtils.split(groups.get(0).getName(), "SG");
                    int number = Integer.parseInt(names[1]) + 1;
                    courseRoundGroup = new CourseRoundGroup(preName + "SG" + Integer.toString(number), code, courseRound);
                }
            }
            
            genericDaoSupport.save(courseRoundGroup);
            
        } else {
            if(courseRound.getOnlineRoundAttr().getOnlineRoundGroupStatus() != null && !courseRound.getOnlineRoundAttr().getOnlineRoundGroupStatus().isDisallowGroup()) {
                courseRound.editGroupStatus(OnlineRoundGroupStatus.GROUPING);
            }
            
        }
        return courseRound;
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.OnlineRoundService#saveUserQrcode(co.fitstart.entity.course.round.CourseRound, org.springframework.web.multipart.MultipartFile)
     */
    public void saveUserQrcode(CourseRound courseRound, MultipartFile qrcode) {
        Result saveUserQrcodeResult = null;
        
        String querySentence = "FROM OrderInvestigateRecord WHERE order = :order";
        OrderInvestigateRecord orderInvestigateRecord = genericDaoSupport.searchForObject(querySentence, "order", courseRound.getOrder(), OrderInvestigateRecord.class);
        
        try {
//            fileHandler.saveQrcode(courseRound.getOrder(), courseRound.getStudent().getUser(), qrcode.getInputStream());
            
            saveUserQrcodeResult = fileHandler.saveUserQrcode(courseRound.getStudent().getUser(), qrcode.getInputStream());
            
            this.saveQrcode(courseRound.getStudent().getUser(), orderInvestigateRecord, saveUserQrcodeResult.getData().get("fileName").toString(), true, null);
            
        } catch (BusinessException e) {
            
            this.saveQrcode(courseRound.getStudent().getUser(), orderInvestigateRecord, null, false, e.getMessage());
            
        } catch (Exception e) {
            
            this.saveQrcode(courseRound.getStudent().getUser(), orderInvestigateRecord, null, false, "上传二维码失败!");
        }
        
        
        
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.OnlineRoundService#saveQrcode(co.fitstart.entity.user.User, co.fitstart.entity.order.OrderInvestigateRecord, java.lang.String, boolean, java.lang.String)
     */
    private void saveQrcode(User user, OrderInvestigateRecord orderInvestigateRecord, String qrcodePath, boolean qrcodeStatus, String qrcodeRemarks) {
        // if save qrcode success
        if(qrcodeStatus == true) {
            // save user qrcode 
            genericDaoSupport.update(user.editWechatQrcode(qrcodePath));
            
            // change qrcode status and set qrcode remarks to null
            genericDaoSupport.update(orderInvestigateRecord.saveQrcodeSuccess());
        } else {
            genericDaoSupport.update(orderInvestigateRecord.saveQrcodeFailure(qrcodeRemarks));
        }
    }

    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.OnlineRoundService#group(java.lang.String)
     */
    public void group(String roundCode) {
        
        // Step1: group old student first
        this.groupByStudentFlag(roundCode, true, "OSG");
        
        // Step: group new student
        this.groupByStudentFlag(roundCode, false, "NSG");
    }
    
    /**
     * 
     * @param roundCode
     * @param oldStudent
     * @param preName
     */
    private void groupByStudentFlag(String roundCode, boolean oldStudent, String preName) {
        List<CourseRound> courseRounds = courseRoundDao.listOnlineRound(OnlineRoundGroupStatus.GROUPING, RoundStatus.PREPARE, roundCode, oldStudent);
        
        int offset = 18;
        int add = courseRounds.size()%offset == 0 ? 0 : 1;
        int groupAmount = courseRounds.size()/offset + add;
        for (int i = 0; i < groupAmount; i++) {
            String name = preName + Integer.toString(i);
            List<CourseRound> rounds = courseRounds.subList(i*offset, (i*offset + offset) > courseRounds.size() ? courseRounds.size() : (i*offset + offset));;
            for(CourseRound courseRound : rounds) {
                CourseRoundGroup courseRoundGroup = new CourseRoundGroup(name, roundCode, courseRound);
                genericDaoSupport.save(courseRoundGroup);
                logger.trace("create courseRoundGroup with code [{}], name [{}], courseRoundId [{}]", roundCode, name, courseRound.getId());
                genericDaoSupport.update(courseRound.editGroupStatus(OnlineRoundGroupStatus.GROUPED));
            }
            
        }
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.OnlineRoundService#loadCourseRoundGroup(co.fitstart.entity.course.round.CourseRound)
     */
    public CourseRoundGroup loadCourseRoundGroup(CourseRound courseRound) {
        return courseRoundDao.loadCourseRoundGroup(courseRound);
    }
    
    /*
     * (non-Javadoc)
     * @see co.fitstart.admin.service.OnlineRoundService#editCourseRoundGroup(co.fitstart.entity.course.round.CourseRound, java.lang.String)
     */
    public void editCourseRoundGroup(CourseRound courseRound, String group) {
        CourseRoundGroup courseRoundGroup = this.loadCourseRoundGroup(courseRound);
        
        if(courseRoundGroup == null) {
            courseRoundGroup = new CourseRoundGroup(group, courseRound.getCode(), courseRound);
            
            genericDaoSupport.save(courseRoundGroup);
            
            logger.trace("create courseRoundGroup with code [{}], name [{}], courseRoundId [{}]", courseRound.getCode(), group, courseRound.getId());
            genericDaoSupport.update(courseRound.editGroupStatus(OnlineRoundGroupStatus.GROUPED));
        } else {
            courseRoundGroup.editName(group);
        }
    }
}
